
local type     = type
local error    = error
local tostring = tostring
local table    = table
local tinsert  = table.insert
local srep     = string.rep
local sformat  = string.format

local function format_k(k)
	local kt = type(k)
	if kt == "number" then
		return sformat("[%d]", k)
	elseif kt == "string" then
		return sformat("[\"%s\"]", k)
	else
		error(k)
	end
end

local function format_line(cache, depth, fmt, ...)
	local s = sformat(fmt, ...)
	tinsert(cache, sformat("%s%s", srep(" ", depth * 2), s))
end

local function _sort_func(v1, v2)
	local v1t, v2t = type(v1), type(v2)
	if v1t == v2t then
		return v1 < v2
	else
		return tostring(v1) < tostring(v2)
	end
end

local function __to_string(t, cache, depth)
	local keys = {}
	for k in pairs(t) do
		keys[#keys + 1] = k
	end
	table.sort(keys, _sort_func)

	for _, k in ipairs(keys) do
		local fk = format_k(k)
		local v = t[k]
		local vt = type(v)
		if vt == "boolean" or vt == "number" then
			format_line(cache, depth, "%s = %s,\n", fk, v)
		elseif vt == "string" then
			format_line(cache, depth, "%s = \"%s\",\n", fk, v)
		elseif vt == "table" then
			format_line(cache, depth, "%s = {\n", fk)
			__to_string(v, cache, depth + 1)
			format_line(cache, depth, "},\n")
		else
			error(v)
		end
	end
end

local function _to_string(t, to_file)
	local cache = to_file and {"return {\n"} or {"{\n"}
	__to_string(t, cache, 1)
	tinsert(cache, "}\n")
	return table.concat(cache)
end

local function save(t, file)
	local f = assert(io.open(file, "w"), file)
	local s = _to_string(t, true)
	f:write(s)
	f:close()
end

return {
	save = save,
	tostring = _to_string,
}